/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package coalescent.cef.command;

import cef.command.Command;
import cef.command.CommandException;
import coalescent.cef.event.DrawGeneTree_Evt;
import coalescent.cef.phylo.JUNG_PLayoutImpl_NodeLabel;
import coalescent.data.K69Data;
import coalescent.phylogeny.GeneTree;
import coalescent.phylogeny.GeneTree.Edge;
import coalescent.phylogeny.GeneTree.Node;
import edu.uci.ics.jung.algorithms.layout.TreeLayout;
import edu.uci.ics.jung.graph.DelegateTree;
import edu.uci.ics.jung.graph.Forest;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.ConstantDirectionalEdgeValueTransformer;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.layout.PersistentLayout;
import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;
import edu.uci.ics.jung.visualization.util.VertexShapeFactory;
import org.apache.commons.collections15.Transformer;
import org.openide.awt.StatusDisplayer;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * Visualizes {@link K69Data} as a gene tree.
 *
 * @author Susanta Tewari
 * @version 1.0 Dec 21, 2010
 */

/**
 * Visualizes {@link K69Data} as a gene tree.
 *
 * @author Susanta Tewari
 * @version 1.0 Dec 21, 2010
 */
public class DrawGeneTree_Cmd extends Command<DrawGeneTree_Evt> {

    private VisualizationViewer<Node, Edge> figure;
    private JPanel figPanel;
    private Forest<Node, Edge> forest;
    protected final GeneTree geneTree;
    protected int offset = 15;
    private PersistentLayout<Node, Edge> pLayout;

    public DrawGeneTree_Cmd(final DrawGeneTree_Evt event) {

        super(event);

        this.geneTree = GeneTree.getInstance(event.getK69Data());
    }

    @Override
    public void executeImpl() throws CommandException {

        forest = new DelegateTree<>();

        forest.addVertex(geneTree.getRoot());
        createJungForest(forest, geneTree.getRoot());
    }

    public String getFigureTitle() {
        return "The phylogenetic tree";
    }

    /**
     * Returns gene tree as an instance of {@link Forest} suitable to be
     * visualized via the JUNG framework.
     *
     * @return a {@code JPanel} with gene tree figure
     */
    public JPanel getFigurePanel() {

        if (figPanel == null) {
            figPanel = decorate_GraphViz((VisualizationViewer<Node, Edge>) getFigure());
        }

        return figPanel;
    }

    private JPanel getFigure() {

        if (figure == null) {
            figure = create_GraphViz(forest, geneTree);
        }

        return figure;
    }

    protected Color getNodeColor(Node node) {
        return Color.WHITE;
    }

    /**
     *
     * @param node
     * @return label to be drawn; if null draws nothing
     */
    protected String getNodeLabel(Node node) {

        String val = node.getLabel();

        if (node.isLeaf()) val += ":" + geneTree.getFreq(node);

        return val;
    }

    protected Shape getVertexShape(Node node, VertexShapeFactory vertexShapeFactory) {
        return vertexShapeFactory.getEllipse(node);
    }

    protected Integer getVertexSize(Node node, VisualizationViewer<Node, Edge> vv) {

        final String val = vv.getRenderContext().getVertexLabelTransformer().transform(node);

        return vv.getFontMetrics(vv.getFont()).stringWidth(val) + offset;
    }

    protected void addComponents(JPanel master) {}

    protected void modifyViewer(VisualizationViewer<Node, Edge> vv) {}

    /**
     * Method description
     *
     *
     * @param tree
     * @param node
     */
    private void createJungForest(final Forest<Node, Edge> tree, final Node node) {

        final List<Node> children = node.getChildren();

        for (final Node child : children) {

            tree.addEdge(node.getChildEdge(child), node, child);

            if (!child.isLeaf()) {
                createJungForest(tree, child);
            }
        }
    }

    private VisualizationViewer<Node, Edge> create_GraphViz(final Forest<Node, Edge> tree,
            final GeneTree gt) {

        pLayout = new JUNG_PLayoutImpl_NodeLabel(gt, new TreeLayout(tree, 100, 100));

        final VisualizationViewer<Node, Edge> vv = new VisualizationViewer<>(pLayout);


        // background
        vv.setBackground(Color.white);


        // adjust vertex label
        vv.getRenderContext().setVertexLabelTransformer(new Transformer<Node, String>() {

            @Override
            public String transform(final Node node) {
                return getNodeLabel(node);
            }

        });


        // adjust vertex color
        vv.getRenderContext().setVertexFillPaintTransformer(new Transformer<Node, Paint>() {

            @Override
            public Paint transform(final Node node) {
                return getNodeColor(node);
            }

        });


        // adjust vertex size
        final VertexShapeFactory vertexShapeFactory = new VertexShapeFactory(new Transformer<Node,
                                                          Integer>() {

            @Override
            public Integer transform(final Node node) {
                return getVertexSize(node, vv);
            }

        }, new Transformer<Node, Float>() {

            @Override
            public Float transform(final Node node) {
                return 1.0f;
            }

        });

        vv.getRenderContext().setVertexShapeTransformer(new Transformer<Node, Shape>() {

            @Override
            public Shape transform(final Node node) {
                return getVertexShape(node, vertexShapeFactory);
            }

        });


        // vertex label position
        vv.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);


        // configure the edge
        vv.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller());
        vv.getRenderContext().setEdgeShapeTransformer(new EdgeShape.Line());
        vv.getRenderContext().setEdgeLabelClosenessTransformer(
            new ConstantDirectionalEdgeValueTransformer(0.5, 0.5));
        modifyViewer(vv);

        return vv;
    }

    private JPanel decorate_GraphViz(final VisualizationViewer<Node, Edge> vv) {


        // mouse: modes & zoom
        final GraphZoomScrollPane zoomPanel     = new GraphZoomScrollPane(vv);    // zoom
        final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse();    // mouse
        final JComboBox modeBox                 = graphMouse.getModeComboBox();    // mouse modes

        vv.setGraphMouse(graphMouse);
        modeBox.addItemListener(graphMouse.getModeListener());
        graphMouse.setMode(ModalGraphMouse.Mode.PICKING);

        //J- fit screen; adjust scroll bar
        final double factor = (vv.getSize().getWidth()) / pLayout.getSize().getWidth();
        new CrossoverScalingControl().scale(vv, (float) factor, vv.getCenter());

        final JScrollBar hScrollBar = zoomPanel.getHorizontalScrollBar();
        hScrollBar.setValue(hScrollBar.getMaximum());    // J+


        // layout: save & restore
        pLayout.setGraph(forest);

        final String layoutPath     = getEvent().getPreferred_PLayoutPath();
        final JButton persist       = new JButton("Save Layout");
        final JButton restore       = new JButton("Restore Layout");
        final boolean layout_exists = new File(layoutPath).exists();

        persist.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {

                try {

                    pLayout.persist(layoutPath);

                    if (!restore.isEnabled()) restore.setEnabled(true);

                } catch (IOException e1) {
                    throw new RuntimeException(e1);
                }
            }

        });
        restore.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {

                try {
                    pLayout.restore(layoutPath);
                } catch (Exception e1) {

                    e1.printStackTrace();
                    restore.setEnabled(false);
                    StatusDisplayer.getDefault().setStatusText("Restore failed -> disabled.");
                }
            }

        });


        // initial restore
        if (layout_exists) {

            try {
                restore.doClick();
            } catch (Exception e) {    // fails (but pLayout is not corrupted)

                new File(layoutPath).delete();    // remove file
                restore.setEnabled(false);    // restore disabled (gets enabled after persist)
            }
        }


        // collect all the controls
        final JToolBar controls = new JToolBar();

        controls.add(modeBox);
        controls.add(persist);
        controls.add(restore);


        // add the controls
        final JPanel geneTreeVisualComp = new JPanel(new BorderLayout());

        geneTreeVisualComp.add(controls, BorderLayout.PAGE_START);
        geneTreeVisualComp.add(zoomPanel, BorderLayout.CENTER);
        addComponents(geneTreeVisualComp);

        return geneTreeVisualComp;
    }
}



